/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System Kernel Module                                  */
/*                                                                           */
/*    Copyright (C) 2011 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    PCF8563 Real time clock driver module                                  */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Includes
#include <krnlTypes.h>
#include <krnlDateTime.h>
#include <drvI2C.h>
#include <drvRealTimeClock.h>
#include <krnlSystemTimer.h>
#include "drvIOConfig.h"

///////////////////////////////////////////////////////////////////////////////
// Constants
#define RTC_GENERATE_FUNCTION_NAME_PREFIX(x,y,z) x ## y ## z
#define RTC_I2C_Start(x,y,z)		RTC_GENERATE_FUNCTION_NAME_PREFIX(drv, x, Start)((y),(z))
#define RTC_I2C_WriteByte(x,y)	RTC_GENERATE_FUNCTION_NAME_PREFIX(drv, x, WriteByte)(y)
#define RTC_I2C_Restart(x,y,z)	RTC_GENERATE_FUNCTION_NAME_PREFIX(drv, x, Restart)((y),(z))
#define RTC_I2C_ReadByte(x,y) 	RTC_GENERATE_FUNCTION_NAME_PREFIX(drv, x, ReadByte)(y)
#define RTC_I2C_Stop(x)					RTC_GENERATE_FUNCTION_NAME_PREFIX(drv, x, Stop)()

// I2C Address
#define RTC_I2C_ADDRESS 0x51

// Register addresses
#define drvPCF8563_REG_ST1         0x00 /* status */
#define drvPCF8563_REG_ST2         0x01

#define drvPCF8563_REG_SC          0x02 /* datetime */
#define drvPCF8563_REG_MN          0x03
#define drvPCF8563_REG_HR          0x04
#define drvPCF8563_REG_DM          0x05
#define drvPCF8563_REG_DW          0x06
#define drvPCF8563_REG_MO          0x07
#define drvPCF8563_REG_YR          0x08

#define drvPCF8563_REG_AMN         0x09 /* alarm */
#define drvPCF8563_REG_AHR         0x0A
#define drvPCF8563_REG_ADM         0x0B
#define drvPCF8563_REG_ADW         0x0C

#define drvPCF8563_REG_CLKO        0x0D /* clock out */
#define drvPCF8563_REG_TMRC        0x0E /* timer control */
#define drvPCF8563_REG_TMR         0x0F /* timer */

// Seconds register (SC) masks
#define drvPCF8563_MSK_SC_LV				0x80 // low voltage mask

#define drvPCF8563_MSK_SEC_MIN			0x7f // seconds/minutes register mask
#define drvPCF8563_MSK_HOUR_DAY			0x3f // Hours/days register mask
#define drvPCF8563_MSK_WEEK_DAY			0x07 // Weekdays register mask
#define drvPCF8563_MSK_MONTH				0x1f // Month register mask			
#define drvPCF8563_MSK_MO_C					0x80 /* century */

///////////////////////////////////////////////////////////////////////////////
// Local variables
static dosDateTime l_date_time;
static dosWord l_timestamp = 0;

///////////////////////////////////////////////////////////////////////////////
// Local functions
static dosByte drvRTCReadAndMaskBCDRegister(dosByte in_mask);

static dosByte bcd2bin(dosByte in_value);
static dosByte bin2bcd (dosByte in_value);

//////////////////////////////////////////////////////////////////////////////
//! Initializes EEPROM interface
void drvRTCInit( void)
{
	InitRTCI2C();
	
	// update datetime
	if( drvRTCIsValid() )
	{
		drvRTCGetDateTime(&l_date_time);
	}
	else
	{
		l_date_time.Hour = 0;
		l_date_time.Minute = 0;
		l_date_time.Second = 0;

		l_date_time.Year = 2011;
		l_date_time.Month = 9;
		l_date_time.Day = 4;
		l_date_time.DayOfWeek = 0;
		
		drvRTCSetDateTime(&l_date_time);
	}
	
	l_timestamp = krnlGetSystemTimer();
}

///////////////////////////////////////////////////////////////////////////////
//! Checks if RTC date/time is valid
//! \return dosTrue - if date/time is valid
dosBool drvRTCIsValid(void)
{
	dosByte seconds;
	
	RTC_I2C_Start(RTC_I2C, drvI2C_WRITE, RTC_I2C_ADDRESS);
	RTC_I2C_WriteByte(RTC_I2C, drvPCF8563_REG_SC);
	RTC_I2C_Restart(RTC_I2C, drvI2C_READ, RTC_I2C_ADDRESS);
	seconds = RTC_I2C_ReadByte(RTC_I2C, drvI2C_NACK);
	RTC_I2C_Stop(RTC_I2C);
	
	return (seconds & drvPCF8563_MSK_SC_LV) == 0;
}	

///////////////////////////////////////////////////////////////////////////////
//! Reads current date/time from RTC
//! \param Structure receiving date/time (if RTC date/time is not valid the date/time structure value will be undefined)
//! \return dosTrue - if date/time is valid
dosBool drvRTCGetDateTime( dosDateTime* out_datetime )
{
	dosByte seconds;
	dosByte month;
	
	RTC_I2C_Start(RTC_I2C, drvI2C_WRITE, RTC_I2C_ADDRESS);
	RTC_I2C_WriteByte(RTC_I2C, drvPCF8563_REG_SC);
	RTC_I2C_Restart(RTC_I2C, drvI2C_READ, RTC_I2C_ADDRESS);
	seconds = RTC_I2C_ReadByte(RTC_I2C, drvI2C_ACK);
	
	// check if date/time is valid
	if( (seconds & drvPCF8563_MSK_SC_LV) != 0 )
	{
		// date/time is invalid -> read next byte and stop communication
		RTC_I2C_ReadByte(RTC_I2C, drvI2C_NACK);
	}
	else
	{
		// date/time is valid read other bytes
		out_datetime->Second = bcd2bin(seconds & drvPCF8563_MSK_SEC_MIN);
		out_datetime->Minute = drvRTCReadAndMaskBCDRegister(drvPCF8563_MSK_SEC_MIN);
		out_datetime->Hour = drvRTCReadAndMaskBCDRegister(drvPCF8563_MSK_HOUR_DAY);
		
		out_datetime->Day = drvRTCReadAndMaskBCDRegister(drvPCF8563_MSK_HOUR_DAY);
		out_datetime->DayOfWeek = drvRTCReadAndMaskBCDRegister(drvPCF8563_MSK_WEEK_DAY);
		month = RTC_I2C_ReadByte(RTC_I2C, drvI2C_ACK);
		out_datetime->Year = bcd2bin(RTC_I2C_ReadByte(RTC_I2C, drvI2C_NACK)) + 1900;
		
		out_datetime->Month = bcd2bin(month & drvPCF8563_MSK_MONTH);
		if( (month & drvPCF8563_MSK_MO_C) != 0 )
			out_datetime->Year += 100;
	}
			
	RTC_I2C_Stop(RTC_I2C);
	
	return (seconds & drvPCF8563_MSK_SC_LV) == 0;
}	

///////////////////////////////////////////////////////////////////////////////
//! Set datetime
//! \param Structure containing time to set
void drvRTCSetDateTime(dosDateTime* in_datetime)
{
	RTC_I2C_Start(RTC_I2C, drvI2C_WRITE, RTC_I2C_ADDRESS);
	RTC_I2C_WriteByte(RTC_I2C, drvPCF8563_REG_SC);

	RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->Second));
	RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->Minute));
	RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->Hour));
	
	RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->Day));
	RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->DayOfWeek));
	
	if (in_datetime->Year > 1999) 
	{
		RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->Month) | 0x80);
		RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->Year - 2000));
	}
	else 
	{
		RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->Month));
		RTC_I2C_WriteByte(RTC_I2C, bin2bcd(in_datetime->Year - 1900));
	}

	RTC_I2C_Stop(RTC_I2C);
}

///////////////////////////////////////////////////////////////////////////////
//! Set datetime
//! \param Structure containing time to set
void drvRTCUpdate(void)
{
	while( krnlGetTimeSince(l_timestamp) > krnlSYSTEM_TIMER_TICKS_PER_SECOND )
  {
  	krnlAddOneSecond( &l_date_time );

   	l_timestamp += krnlSYSTEM_TIMER_TICKS_PER_SECOND;
	}
}


/*****************************************************************************/
/* Local functions                                                           */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Reads BCD value masks it and converts to decimal value
//! \param Mask value
//! \return Binary value
static dosByte drvRTCReadAndMaskBCDRegister(dosByte in_mask)
{
	return bcd2bin(RTC_I2C_ReadByte(RTC_I2C, drvI2C_ACK) & in_mask);
}		

///////////////////////////////////////////////////////////////////////////////
//! Convert BCD to binary
//! \param Two digit BCD value
//! \return Binary value
static dosByte bcd2bin(dosByte in_value)
{
	return ((((in_value >> 4) & 0x0F) * 10) + (in_value & 0x0F));
}

///////////////////////////////////////////////////////////////////////////////
//! Convert binary to BCD 
//! \param Binary number (two digit numbers only [0..99])
//! \return Binary value
static dosByte bin2bcd (dosByte in_value)
{
	return (((in_value / 10) << 4) | (in_value % 10));
}
